package br.jm.i18n;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

public final class I18NRepository {

	static final Method DEFINE_CLASS;

	static {
		try {
			DEFINE_CLASS = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
			DEFINE_CLASS.setAccessible(true);
		} catch (final Throwable e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	@SuppressWarnings({ "PMD", "all" })
	static InputStream getClassFile(final Class<?> c) {
		final Class<?> enc = c.getEnclosingClass();

		if (enc == null) {
			return c.getResourceAsStream(c.getSimpleName() + ".class");
		}

		String name = c.getName();

		final int lastDot = name.lastIndexOf('.');

		name = ((lastDot > -1) ? name.substring(lastDot + 1) : name) + ".class";

		return c.getResourceAsStream(name);
	}

	final MessageProvider globalProvider;

	public I18NRepository(final MessageProvider globalProvider) {
		this.globalProvider = globalProvider;
	}

	Class<?> defineClass(final ClassLoader cl, final byte[] buffer) {
		try {
			return (Class<?>) DEFINE_CLASS.invoke(cl, null, buffer, 0, buffer.length);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}

	public <T> T getMessageContainer(final Class<T> clazz, final Locale loc) {
		T instance = resolve(clazz, loc);

		if (instance == null) {
			instance = spin(clazz, loc);
		}

		return instance;
	}

	MessageProvider getProvider(final Class<?> clazz, final Locale loc) {
		return globalProvider == null ? new ResourceBundleMessageProvider(clazz, loc) : globalProvider;
	}

	@SuppressWarnings("unchecked")
	public <T> T resolve(final Class<T> clazz, final Locale loc) {
		try {
			final Class<?> impl = Class.forName(clazz.getName() + "@" + loc, true, clazz.getClassLoader());

			return (T) impl.newInstance();
		} catch (final ClassNotFoundException e) {
			return null;
		} catch (final ReflectiveOperationException e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public <T> T spin(final Class<T> clazz, final Locale loc) {
		synchronized (clazz) {
			final T instance = resolve(clazz, loc);

			if (instance != null) {
				return instance;
			}

			try (InputStream is = getClassFile(clazz)) {

				final ClassReader cr = new ClassReader(is);
				final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
				final MessageProvider mp = getProvider(clazz, loc);
				final I18NGenerator gen = new I18NGenerator(cw, mp, loc);

				cr.accept(gen, ClassReader.SKIP_DEBUG);

				final byte[] bc = cw.toByteArray();

				final Class<?> define = defineClass(clazz.getClassLoader(), bc);

				return (T) define.newInstance();

			} catch (IOException | InstantiationException | IllegalAccessException e) {
				throw new RuntimeException(e);
			}
		}
	}
}
